﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Xml;
using System.Reflection;
using System.Reflection.Emit;
using System.Security;
using System.Security.Policy;
using System.Security.Permissions;

namespace KC.Presentation.WorkTool
{
	public partial class DependencyFinder : UserControl
	{
		public DependencyFinder()
		{ InitializeComponent(); }

		protected List<string> MissingFiles = new List<string>();
		protected int
			RefsFound = 0,
			RefsInGac = 0,
			RefsNotFound = 0,
			RefsInFramework = 0;

		private void btnSlnBrowse_Click(object sender, EventArgs e)
		{
			OpenFileDialog ofd = new OpenFileDialog();
			ofd.DefaultExt = "sln";
			ofd.Multiselect = false;
			if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
			{
				txtInputFile.Text = ofd.FileName;
				if (txtInputFile.Text.Contains("\\"))
					txtDllFolder.Text = txtInputFile.Text.Substring(0,txtInputFile.Text.LastIndexOf("\\"));
			}
		}
		private void btnGo_Click(object sender, EventArgs e)
		{
			string
				filename = txtInputFile.Text,
				foldername = txtDllFolder.Text;
			bool exists = false;

			// verify file
			try { exists = System.IO.File.Exists(filename); }
			catch { }

			if (!exists) {
				MessageBox.Show("File does not exist");
				return; }

			// verify folder
			exists = false;
			try {
				exists = System.IO.Directory.Exists(foldername); }
			catch { }

			if (!exists)
				foldername = "";
			else if (!foldername.EndsWith(@"\"))
				foldername += @"\";

			if (new FileInfo(txtInputFile.Text).Extension.ToLower() == ".dll")
				HandleDll(filename, foldername);
			else if (new FileInfo(txtInputFile.Text).Extension.ToLower() == ".sln")
				HandleSln(filename, foldername);
			else
				MessageBox.Show("Invalid file extension. Feed me a .DLL or .SLN file.");
		}

		private void HandleSln(string filename, string foldername)
		{
			// Open and read SLN file
			string[] SlnContent = System.IO.File.ReadAllLines(filename);
			string dir = new FileInfo(filename).DirectoryName + "\\"; 

			// Build a list of .csproj files
			List<string> Projects = new List<string>();
			foreach (string line in SlnContent)
			{
				if (!line.StartsWith("Project(\"")) continue;

				#region Handle web projects

				if (line.ToLower().Contains("http://"))
				{
					string file = line.Split(',')[1].Trim().Substring(line.Split(',')[1].Trim().LastIndexOf("/") + 1).TrimEnd('\"').Trim();
					string Location = "";
					AskQuestion aq = new AskQuestion("Enter the physical path to the project named \n" + file + "\nDo not include the .csproj file");
					if (aq.ShowDialog() == DialogResult.OK)
					{
						bool LocationExists = false;
						try { LocationExists = System.IO.Directory.Exists(aq.Answer); }
						catch {
							MessageBox.Show("Location does not exist, skipping project\n" + Location);
							continue; }
						if (!LocationExists) {
							MessageBox.Show("Location does not exist, skipping project\n" + Location);
							continue; }

						Location = aq.Answer.Trim();
						if (!Location.EndsWith("\\")) Location += "\\";

						bool FileExists = false;
						try {
							FileExists = System.IO.File.Exists(Location + file); }
						catch {
							MessageBox.Show("File does not exist; skipping project\n" + Location + file);
							FileExists = false;
							continue; }
						if (!FileExists) {
							MessageBox.Show("File does not exist; skipping project\n" + Location + file);
							continue; }

						Projects.Add(new FileInfo(Location + file).DirectoryName + "\\" + file);
					}
					continue;
				}

				#endregion Handle web projects

				string[] parts = line.Split(',');
				Projects.Add(dir + parts[1].Replace("\"", "").Trim());
			}

			List<csproj> Projects2 = GetProjectInfo(filename, Projects);
			// Display project listing
			txtOutputProjectList.Text = "Project listing: \r\n";

			BuildTabPage_AssembliesByProject(Projects2);
			BuildTabPage_AssembliesAlpha(Projects2);
			BuildTabPage_ProjectList(Projects);
			BuildTabPage_AssemblyOutputList();
			BuildTabPage_MissingFiles(Projects2, foldername);

		}
		private void HandleDll(string filename, string foldername)
		{
			// Build Assembly Tree
			tvAssemblies.Nodes.Clear();
			TreeNode tn = BuildTabPage_AssemblyTree(filename, foldername);
			if (tn != null) 
				tvAssemblies.Nodes.Add(tn);

			// Build Dependencies by Project
			BuildTabPage_AssembliesByProject(filename);
		}


		private TreeNode BuildTabPage_AssemblyTree(string filename, string foldername)
		{
			RefsFound = 0;
			RefsInGac = 0;
			RefsNotFound = 0;
			RefsInFramework = 0;
			MissingFiles = new List<string>();
			tvAssemblies.Nodes.Clear();

			Assembly assembly;
			try
			{
				assembly = Assembly.LoadFrom(filename);
			}
			catch (Exception ex) 
			{
				MessageBox.Show("Error loading assembly " + filename + "\n" + ex.Message);
				return null;
			}
			TreeNode tRoot = BuildNode(assembly, foldername);
			
			txtResults.Text =
				RefsFound.ToString() + " References Located in Filesystem\r\n" +
				RefsInFramework.ToString() + " References Located in Framework\r\n" +
				RefsInGac.ToString() + " References Located in GAC\r\n" +
				RefsNotFound.ToString() + " References Not Found: \r\n\r\n";
			foreach (string s in MissingFiles)
				txtResults.Text += s + "\r\n";
			return tRoot;
		}
		private void BuildTabPage_AssembliesByProject(List<csproj> Projects2)
		{	// Handles SLNs
			txtOutputDBP.Text = "";
			foreach (csproj Project in Projects2)
			{
				txtOutputDBP.Text += Project.Filename + "\r\n";
				foreach (string dependency in Project.DependencyDLLs)
					txtOutputDBP.Text += "  " + dependency + "\r\n";
				txtOutputDBP.Text += "\r\n";
			}
		}
		private void BuildTabPage_AssembliesByProject(string filename)
		{	// Handles DLLs
			Assembly assembly;
			try
			{
				assembly = Assembly.LoadFrom(filename);
			}
			catch (Exception ex)
			{
				MessageBox.Show("Error loading assembly " + filename + "\n" + ex.Message);
				return;
			}
			txtOutputDBP.Text = "";
			AssemblyName[] assemblies = assembly.GetReferencedAssemblies();
			foreach (AssemblyName a in assemblies)
				txtOutputDBP.Text += a.Name + ".dll\r\n";
		}
		private void BuildTabPage_AssembliesAlpha(List<csproj> Projects2)
		{	// Handles SLNs
			List<string> AllDependencies = new List<string>();
			foreach (csproj Proj in Projects2)
				foreach (string dll in Proj.DependencyDLLs)
					if (!AllDependencies.Contains(dll.ToLower()))
						AllDependencies.Add(dll.ToLower());
			AllDependencies.Sort();
			txtOutputAlpha.Text = "";
			foreach (string str in AllDependencies)
				txtOutputAlpha.Text += str + "\r\n";
		}
		private void BuildTabPage_MissingFiles(List<csproj> Projects2, string foldername)
		{
			if (string.IsNullOrEmpty(foldername)) 
			{
				txtOutputMissingFiles.Text = "No folder passed in, skipping this report";
				return;
			}

			txtOutputMissingFiles.Text = "";
			int found = 0, total = 0;

			foreach (csproj Project in Projects2)
			{
				// txtOutputMissingFiles.Text += Project.foldername + "\r\n";
				foreach (string dependency in Project.DependencyDLLs)
				{
					// txtOutputMissingFiles.Text += " - " + dependency + "\r\n";
					string path = foldername + new FileInfo(dependency).Name;
					total++;
					if (!System.IO.File.Exists(path) && !dependency.ToLower().Contains("system."))
						txtOutputMissingFiles.Text += path + "\r\n";
				}
			}

			txtOutputMissingFiles.Text += "\r\n" + found.ToString() + " of " + total.ToString() + " references by assembly (DLLs) are missing";
		}
		private void BuildTabPage_ProjectList(List<string> Projects)
		{
			txtOutputProjectList.Text = "";
			foreach (string csproj in Projects)
				txtOutputProjectList.Text += csproj + "\r\n";
			
		}
		private void BuildTabPage_AssemblyOutputList()
		{
			// TODO: txtOutputAssemblyList
		}

		private TreeNode BuildNode(Assembly assembly, string foldername)
		{
			TreeNode tn = new TreeNode(assembly.GetName().Name);
			tn.ToolTipText = assembly.FullName + "\n" + assembly.CodeBase;
			AssemblyName[] assemblies = assembly.GetReferencedAssemblies();

			foreach (AssemblyName a in assemblies)
			{
				string TruePath = FindFilePath(a.Name + ".dll", foldername, cbRecursiveFolders.Checked);
				TreeNode tn2 = null;
				if (!string.IsNullOrEmpty(TruePath))
				{ // File found in folder
					RefsFound++;
                    try
                    {
						Assembly assy = Assembly.LoadFile(TruePath);
						if (cbRecursiveDLLs.Checked)
							tn2 = BuildNode(assy, foldername);
						else
						{
							tn2 = new TreeNode(assy.GetName().Name);
							tn2.ToolTipText = assy.FullName + "\n" + assy.CodeBase + "\r\n" +
								"(Recursive DLL lookups is disabled)";
						}
                    }
                    catch (FileLoadException)
                    {
						tn2 = new TreeNode(TruePath.Substring(TruePath.LastIndexOf("\\") + 1));
                        tn2.ToolTipText = "Could not load assembly due to security restrictions.";
                    }
					tn.Nodes.Add(tn2);
				}
				else if (a.Name.StartsWith("System"))
				{ // Framework assemblies not included
					RefsInFramework++;
					tn2 = new TreeNode();
					tn2.Text = a.Name;
					tn2.ForeColor = System.Drawing.Color.Green;
					tn2.ToolTipText += "\n.NET Framework File";
					tn.Nodes.Add(tn2);
				}
				else
				{
					try
					{   // Find file in GAC
						tn.ToolTipText = assembly.FullName + "\n" + assembly.CodeBase + "\nFile detected in GAC";
						Assembly assy = Assembly.Load(a);
						if (cbRecursiveDLLs.Checked)
							tn2 = BuildNode(assy, foldername);
						//tn2 = new TreeNode(a.Name);
						//tn2.Text = "(" + filename2.Substring(TrueName.LastIndexOf("\\") + 1) + ")";
						//tn2.ForeColor = System.Drawing.Color.Green;
						RefsInGac++;
					}
					catch (System.IO.FileNotFoundException)
					{   // File not Found
						RefsNotFound++;
						if (!MissingFiles.Contains(a.Name))
							MissingFiles.Add(a.Name);
						tn2 = new TreeNode();
						tn2.Text = "(" + a.Name + ".dll)\r\n";
						tn2.ToolTipText = "File Not Found";
						tn2.ForeColor = System.Drawing.Color.Red;
					}
					if (tn2 != null)
						tn.Nodes.Add(tn2);
				}
                tvAssemblies.Sort();
			}
            
			return tn;
		}
		private string FindFilePath(string filename, string foldername, bool recursive)
		{
			DirectoryInfo di = new DirectoryInfo(foldername);

			FileInfo[] files = di.GetFiles(filename, 
				recursive
				? SearchOption.AllDirectories
				: SearchOption.TopDirectoryOnly);

			return (files.Length > 0 ? files[0].FullName : null);
		}

		public List<csproj> GetProjectInfo(string SlnFile, List<string> files)
		{
			List<csproj> projects = new List<csproj>();
			foreach (string file in files)
			{
				csproj proj = new csproj();
				proj.Filename = file;
				ResolveDependencies(SlnFile, ref proj);
				projects.Add(proj);
			}
			return projects;
		}
		public void ResolveDependencies(string SlnFile, ref csproj project)
		{
			XmlDocument xDoc = new XmlDocument();
			xDoc.Load(project.Filename);
			project.DependencyDLLs.Clear();
			string SlnFolder = new FileInfo(SlnFile).DirectoryName;

			foreach (XmlNode xNode in xDoc.SelectNodes("/VisualStudioProject/CSHARP/Build/References/Reference"))
			{
				// Add assembly reference dependencies
				if (xNode.Attributes["HintPath"] != null)
				{
					string file = xNode.Attributes["HintPath"].InnerText;
					if (!file.ToLower().Contains("system."))
						file = new FileInfo(project.Filename).DirectoryName + "\\" + file;
					project.DependencyDLLs.Add(new System.IO.FileInfo(file).FullName);
				}
				// Add project reference dependencies
				else
				{   // TODO
				}
			}
		}

        private void trackBar1_Scroll(object sender, EventArgs e)
        {
			if (tvAssemblies.Nodes.Count == 0)
				return;

            if (trackBar1.Value == trackBar1.Maximum)
            {
                tvAssemblies.ExpandAll();
                return;
            }
            else if (trackBar1.Value == trackBar1.Minimum)
            {
                tvAssemblies.CollapseAll();
                return;
            }
            ExpandNode(tvAssemblies.Nodes[0], 0, trackBar1.Value);
			if (tvAssemblies.Nodes.Count > 0)
				splitContainer1.Panel1.ScrollControlIntoView(tvAssemblies);
        }
        private void ExpandNode(TreeNode n, int CurLevel, int TargetLevel)
        {
            if (CurLevel > TargetLevel)
                return;
            foreach (TreeNode n2 in n.Nodes)
            {
				n.Expand();
                ExpandNode(n2, CurLevel + 1, TargetLevel);
            }
        }

	}
	public class csproj
	{
		public List<string> DependencyDLLs = new List<string>();
		public List<csproj> DependencyProjects = new List<csproj>();
		public string Filename = "";
	}
}

// TODO: All places that find an assembly by filename string 
// should be enhanced to load the assembly and chase it down
